﻿#define USE_POOL
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#pragma warning disable 0414
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[DisallowMultipleComponent]
public class EffectMeshUtility : MonoBehaviour
{
    static public void FreePool()
    {
        foreach (var item in s_matPool)
        {
            if (item.Value != null)
            {
                UnityEngine.Object.Destroy(item.Value);
            }
        }
        s_matPool.Clear();
        s_listPool.Clear();
    }

    static Dictionary<int, Material> s_matPool = new Dictionary<int, Material>();
    static Material GetMat(Material mat, string define)
    {
        Material outMat;
        if (!s_matPool.TryGetValue(mat.GetInstanceID(), out outMat))
        {
            outMat = Material.Instantiate(mat);
            outMat.EnableKeyword(define);
            outMat.renderQueue += Random.Range(1, MatQueue);
            s_matPool.Add(mat.GetInstanceID(), outMat);
        }
        return outMat;
    }

    static Dictionary<int, List<Vector4>> s_listPool = new Dictionary<int, List<Vector4>>();
    public static List<Vector4> GetList(int maxElement)
    {
        if (maxElement <= 0)
            return null;

        List<Vector4> outList;
        if (!s_listPool.TryGetValue(maxElement, out outList))
        {
            outList = new List<Vector4>(maxElement);
            s_listPool.Add(maxElement, outList);
        }
        return outList;
    }

    static string strPackedOff = "VERTEX_PACKED_OFF";

    public bool IsForceVertexPacked = false;
    public const int MaxVertex = 20;
    public const int MatQueue = 1;
    public const float TexOffsetRange = 10.0f;
    public const float TexScaleRange = 10.0f;
    public const float mColorRange = 10.0f;

    public static float V2Tofloat(float v_x, float v_y, float c_precision = 1024)
    {
        v_x = Mathf.Clamp(v_x, 0.0f, 0.9999f);
        v_y = Mathf.Clamp01(v_y);

        return v_x + Mathf.Floor(v_y * c_precision + 0.5f);
    }

    static public float PackM(float mColor_x, float mColor_y, float mColorRange)
    {
        float rg_x = Mathf.Clamp(mColor_x, 0, mColorRange) / mColorRange;
        float rg_y = Mathf.Clamp(mColor_y, 0, mColorRange) / mColorRange;

        return V2Tofloat(rg_x, rg_y);
    }

    static public float PackScale(Vector2 tiling, float TexScaleRange)
    {
        tiling.x = Mathf.Clamp(tiling.x, -TexScaleRange, TexScaleRange);
        tiling.y = Mathf.Clamp(tiling.y, -TexScaleRange, TexScaleRange);

        tiling += new Vector2(TexScaleRange, TexScaleRange);
        tiling = tiling / (2.0f * TexScaleRange);
        return V2Tofloat(tiling.x, tiling.y);
    }

    static public float PackOffset(Vector2 offset, float TexOffsetRange)
    {
        offset.x = Mathf.Clamp(offset.x, -TexOffsetRange, TexOffsetRange);
        offset.y = Mathf.Clamp(offset.y, -TexOffsetRange, TexOffsetRange);

        offset += new Vector2(TexOffsetRange, TexOffsetRange);
        offset = offset / (2.0f * TexOffsetRange);

        return V2Tofloat(offset.x, offset.y);
    }

    public Color tintColor = Color.white;
    public float mMultiplier = 1.0f;

    public bool mainTexIsClamp = false;
    public Vector2 mainTiling = Vector2.one;
    public Vector2 mainOffset = Vector2.zero;
    public Vector4 MainBlock = new Vector4(1, 1, 0, 0);

    public bool isMask = false;
    public bool maskTexIsClamp = false;
    public Vector2 maskTiling = Vector2.one;
    public Vector2 maskOffset = Vector2.zero;
    public Vector4 MaskBlock = new Vector4(1, 1, 0, 0);

    public bool isRimlight = false;
    public Color rimColor = Color.white;
    public float rimFalloff = 1.0f;
    public float rimPower = 1.0f;
    public float alphaPower = 1.0f;

    public bool isDISTORT = false;
    public bool distortTexIsClamp = false;
    public Vector2 distortTiling = Vector2.one;
    public Vector2 distortOffset = Vector2.zero;
    public Vector4 DistortBlock = new Vector4(1, 1, 0, 0);
    public float _forceX = 0.1f;
    public float _forceY = 0.1f;
    public float _heatTime = 0.0f;

    public MeshFilter filter = null;
    public Renderer meshRenderer = null;

    [System.NonSerialized]
    private Mesh sharedMesh = null;
    [System.NonSerialized]
    private Material sharedMaterial = null;


    public static int pack0ID = Shader.PropertyToID("pack0");
    public static int pack1ID = Shader.PropertyToID("pack1");
    public static int pack2ID = Shader.PropertyToID("pack2");
    public static int pack3ID = Shader.PropertyToID("pack3");
    public static int pack4ID = Shader.PropertyToID("pack4");

    [System.NonSerialized]
    public MaterialPropertyBlock materialBlock = null;

    [System.NonSerialized]
    public List<Vector4> uvBuffer = null;

    [System.NonSerialized]
    private Color m_oldColor;
    [System.NonSerialized]
    private float m_oldM;

    [System.NonSerialized]
    private Vector2 m_oldMainTiling;
    [System.NonSerialized]
    private Vector2 m_oldMainOffset;

    [System.NonSerialized]
    private Vector2 m_oldMaskTiling;
    [System.NonSerialized]
    private Vector2 m_oldMaskOffset;

    private void Awake()
    {
        if (filter == null)
            filter = gameObject.GetComponent<MeshFilter>();
        if (meshRenderer == null)
            meshRenderer = gameObject.GetComponent<Renderer>();
    }

    public bool IsVBPacked()
    {
        MeshFilter ft = gameObject.GetComponent<MeshFilter>();
        if (ft == null || ft.sharedMesh == null)
            return false;

        return IsForceVertexPacked || ft.sharedMesh.vertexCount <= MaxVertex;
    }

    public static Vector4 vector4Null = new Vector4(float.MinValue, float.MinValue, float.MinValue, float.MinValue);
    public static Vector2 vector2Null = new Vector2(float.MinValue, float.MinValue);

    [System.NonSerialized]
    Vector2 _pack0 = vector2Null;
    [System.NonSerialized]
    public Vector4 _pack1 = vector4Null;
    [System.NonSerialized]
    public Vector4 _pack2 = vector4Null;

    public void InitMeshAndMaterial()
    {
        if (meshRenderer == null)
            meshRenderer = GetComponent<Renderer>();

        if (filter == null)
            filter = GetComponent<MeshFilter>();

        if (filter == null || meshRenderer == null)
            return;

        if (sharedMesh != null)
            return;

        float a1 = V2Tofloat(MainBlock.x, MainBlock.y);
        float a2 = V2Tofloat(MainBlock.z, MainBlock.w);
        _pack0 = new Vector2(a1, a2);

        sharedMesh = filter.sharedMesh;

        if(materialBlock == null)
            materialBlock = new MaterialPropertyBlock();
        meshRenderer.SetPropertyBlock(materialBlock);

        if (!sharedMesh.isReadable 
            || (!IsForceVertexPacked && filter.sharedMesh.vertexCount > MaxVertex))
        {
            sharedMaterial = meshRenderer.sharedMaterial;

            if (!sharedMaterial.IsKeywordEnabled(strPackedOff))
            {
#if USE_POOL
                meshRenderer.sharedMaterial = GetMat(sharedMaterial, strPackedOff);
#else
                meshRenderer.sharedMaterial = Material.Instantiate(sharedMaterial);
                meshRenderer.sharedMaterial.EnableKeyword(strPackedOff);
#endif
            }
        }
        else
        {
            Mesh cloneMesh = Mesh.Instantiate(sharedMesh);
            filter.sharedMesh = cloneMesh;
#if USE_POOL
            uvBuffer = GetList(sharedMesh.vertexCount);
#else
            uvBuffer = new List<Vector4>(sharedMesh.vertexCount);
#endif
            filter.sharedMesh.GetUVs(0, uvBuffer);
            for (int i = 0; i < uvBuffer.Count; i++)
            {
                Vector4 tex = uvBuffer[i];
                tex.z = _pack0.x;
                tex.w = _pack0.y;
                uvBuffer[i] = tex;
            }
            filter.sharedMesh.SetUVs(0, uvBuffer);
        }
    }

    public void ForceInit()
    {
        meshRenderer = gameObject.GetComponent<Renderer>();
        filter = gameObject.GetComponent<MeshFilter>();
    }

    public int EvaluationVBByte()
    {
        MeshFilter mf = GetComponent<MeshFilter>();
        if (mf == null && mf.sharedMesh == null)
            return 0;

        int verticesCount = mf.sharedMesh.vertexCount;
        int arr = 16;
        if (isMask)
            arr += 4;
        return arr * verticesCount;
    }

    void OnEnable()
    {
        Vector4 color = tintColor.linear;
        color = Mathf.Min(mColorRange, mMultiplier) * color;
        float c1 = EffectMeshUtility.PackM(color.x, color.y, mColorRange);
        float c2 = EffectMeshUtility.PackM(color.z, color.w, mColorRange);
        c1 = mainTexIsClamp ? -c1 : c1;
        c2 = maskTexIsClamp ? -c2 : c2;
        float d1 = PackScale(mainTiling, TexScaleRange);
        float d2 = PackOffset(mainOffset, TexOffsetRange);
        _pack1 = new Vector4(c1, c2, d1, d2);

        _pack2 = Vector4.zero;
        if (isMask)
        {
            float b1 = V2Tofloat(MaskBlock.x, MaskBlock.y);
            float b2 = V2Tofloat(MaskBlock.z, MaskBlock.w);
            float e1 = PackScale(maskTiling, TexScaleRange);
            float e2 = PackOffset(maskOffset, TexOffsetRange);
            _pack2 = new Vector4(b1, b2, e1, e2);
        }

        Vector4 _pack3 = Vector4.zero;
        if (isRimlight)
        {
            Vector4 rim = rimColor.linear;
            float f1 = V2Tofloat(rim.x, rim.y);
            float f2 = V2Tofloat(rim.z, rim.w);
            float g1 = EffectMeshUtility.PackM(rimFalloff, rimPower, mColorRange);
            float g2 = alphaPower;
            _pack3 = new Vector4(f1, f2, g1, g2);
        }

        Vector4 _pack4 = Vector4.zero;
        if (isDISTORT)
        {
            float b1 = V2Tofloat(DistortBlock.x, DistortBlock.y);
            float b2 = V2Tofloat(DistortBlock.z, DistortBlock.w);
            float f1 = V2Tofloat(_forceX, _forceY);
            float f2 = _heatTime;
            _pack4 = new Vector4(b1, b2, f1, f2);
        }

#if !UNITY_EDITOR
        if (Application.isPlaying)
#endif
        {
            m_oldColor = tintColor;
            m_oldM = mMultiplier;

            m_oldMainTiling = mainTiling;
            m_oldMainOffset = mainOffset;

            m_oldMaskTiling = maskTiling;
            m_oldMaskOffset = maskOffset;

            InitMeshAndMaterial();

            meshRenderer.enabled = true;

            bool bCopyData = (uvBuffer != null && filter != null && filter.sharedMesh != null);
            if (bCopyData)
            {
                for (int i = 0; i < uvBuffer.Count; i++)
                {
                    uvBuffer[i] = _pack1;
                }
                filter.sharedMesh.SetUVs(1, uvBuffer);

                if (isMask)
                {
                    for (int i = 0; i < uvBuffer.Count; i++)
                    {
                        uvBuffer[i] = _pack2;
                    }
                    filter.sharedMesh.SetUVs(2, uvBuffer);
                }
            }

            if (materialBlock != null)
            {
                if (!bCopyData)
                {
                    materialBlock.SetVector(pack0ID, _pack0);
                    materialBlock.SetVector(pack1ID, _pack1);
                    materialBlock.SetVector(pack2ID, _pack2);
                }

                if(isRimlight)
                    materialBlock.SetVector(pack3ID, _pack3);

                if(isDISTORT)
                    materialBlock.SetVector(pack4ID, _pack4);
            }
        }
    }
    void OnDisable()
    {
        if (Application.isPlaying)
        {
            tintColor = m_oldColor;
            mMultiplier = m_oldM;
            mainTiling = m_oldMainTiling;
            mainOffset = m_oldMainOffset;

            maskTiling = m_oldMaskTiling;
            maskOffset = m_oldMaskOffset;
        }
#if UNITY_EDITOR
        else
            OnDestroy();
#endif
    }
    void OnDestroy()
    {
        if (filter != null && sharedMesh != filter.sharedMesh)
        {
            Object.DestroyImmediate(filter.sharedMesh);
            filter.sharedMesh = sharedMesh;
        }

        if (meshRenderer != null)
        {
            meshRenderer.sharedMaterial = sharedMaterial;
            meshRenderer.SetPropertyBlock(null);
        }

        sharedMesh = null;
        sharedMaterial = null;
        materialBlock = null;
        uvBuffer = null;
    }
}
